#region Namespace Declarations

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

#endregion Namespace Declarations

namespace RSEngine.Math
{
    /// <summary>
    /// 4D homogeneous vector.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct Vector4
    {

        #region variables

        public float x, y, z, w;

        public static readonly Vector4 Zero = new Vector4(0.0f, 0.0f, 0.0f, 0.0f);
        public static readonly Vector4 Identity = new Vector4(0.0f, 0.0f, 0.0f, 1.0f);

        #endregion

        #region Constructors

        /// <summary>
        ///	Creates a new 4 dimensional Vector.
        /// </summary>
        public Vector4(float x, float y, float z, float w)
        {
            this.x = x;
            this.y = y;
            this.z = z;
            this.w = w;
        }

        /// <summary>
        ///	Creates a new 4 dimensional Vector.
        /// </summary>
        public Vector4(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
            this.w = 1f;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the length (magnitude) of this Vector4.  The Sqrt operation is expensive, so 
        /// only use this if you need the exact length of the Vector.  If vector lengths are only going
        /// to be compared, use LengthSquared instead.
        /// </summary>
        public float Length
        {
            get
            {
                return (float)System.Math.Sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
            }
        }

        /// <summary>
        ///    Returns the length (magnitude) of the vector squared.
        /// </summary>
        public float LengthSquared
        {
            get
            {
                return (this.x * this.x + this.y * this.y + this.z * this.z);
            }
        }

        #endregion

        #region Normalize / ToNormalized

        public Vector4 ToNormalized()
        {
            Vector4 vec = this;
            vec.Normalize();
            return vec;
        }

        /// <summary>
        ///	Normalizes the vector.
        /// </summary>
        /// <remarks>
        ///	This method normalises the vector such that it's
        ///	length / magnitude is 1. The result is called a unit vector.
        ///	<p/>
        ///	This function will not crash for zero-sized vectors, but there
        ///	will be no changes made to their components.
        ///	</remarks>
        ///	<returns>The previous length of the vector.</returns>
        public float Normalize()
        {
            float length = (float)System.Math.Sqrt(this.x * this.x + this.y * this.y + this.z * this.z);

            // Will also work for zero-sized vectors, but will change nothing
            if (length > float.Epsilon)
            {
                float inverseLength = 1.0f / length;

                this.x *= inverseLength;
                this.y *= inverseLength;
                this.z *= inverseLength;
            }

            return length;
        }

        #endregion

        #region Dot

        /// <summary>
        ///	Performs a Dot Product operation on 2 vectors, which produces the angle between them.
        /// </summary>
        public float Dot(Vector4 vector)
        {
            return x * vector.x + y * vector.y + z * vector.z + w * vector.w;
        }

        #endregion

        #region Cross

        /// <summary>
        ///	Performs a Cross Product operation on 2 vectors, which returns a vector that is perpendicular
        ///	to the intersection of the 2 vectors.  Useful for finding face normals.
        /// </summary>
        public Vector4 Cross(Vector4 vector)
        {
            return new Vector4(
                (this.y * vector.z) - (this.z * vector.y),
                (this.z * vector.x) - (this.x * vector.z),
                (this.x * vector.y) - (this.y * vector.x)
                );
        }

        #endregion

        #region Reflect

        /// <summary>
        /// Calculates a reflection vector to the plane with the given normal.
        /// </summary>
        /// <remarks>
        /// Assumes this vector is pointing AWAY from the plane, invert if not.
        /// </remarks>
        /// <param name="normal">Normal vector on which this vector will be reflected.</param>
        /// <returns></returns>
        public Vector4 Reflect(Vector4 normal)
        {
            return this - (2 * this.Dot(normal) * normal);
        }

        #endregion

        #region Transmit

        public static Vector4 Transmit(Vector4 rayDirection, Vector4 normal, float n1, float n2)
        {
            Vector4 t;
            float w, r, k;

            rayDirection = rayDirection.Clone();
            normal = normal.Clone();
            rayDirection.Normalize();
            normal.Normalize();
            if (normal.Dot(rayDirection) > 0)
            {
                normal = -normal;
                r = n1;
                n1 = n2;
                n2 = r;
            }
            r = n1 / n2;
            w = -rayDirection.Dot(normal) * r;
            k = 1f + (w - r) * (w + r);

            // Check for total internal reflection
            if (k < 0.0)
                return -rayDirection.Reflect(normal);

            k = (float)System.Math.Sqrt(k);
            t = r * rayDirection + (w - k) * normal;
            t.Normalize();

            return t;
        }

        #endregion

        #region Operator overloads + CLS compliant method equivalents

        /// <summary>
        ///	Used when a Vector4 is multiplied by a scalar value.
        /// </summary>
        public static Vector4 operator *(Vector4 left, float scalar)
        {
            return new Vector4(left.x * scalar, left.y * scalar, left.z * scalar, left.w * scalar);
        }

        /// <summary>
        ///	Used when a scalar value is multiplied by a Vector4.
        /// </summary>
        public static Vector4 operator *(float scalar, Vector4 right)
        {
            return new Vector4(right.x * scalar, right.y * scalar, right.z * scalar, right.w * scalar);
        }

        /// <summary>
        ///		
        /// </summary>
        /// <param name="vector"></param>
        /// <param name="matrix"></param>
        /// <returns></returns>
        public static Vector4 operator *(Matrix4 matrix, Vector4 vector)
        {
            Vector4 result = new Vector4();

            result.x = vector.x * matrix.M00 + vector.y * matrix.M01 + vector.z * matrix.M02 + vector.w * matrix.M03;
            result.y = vector.x * matrix.M10 + vector.y * matrix.M11 + vector.z * matrix.M12 + vector.w * matrix.M13;
            result.z = vector.x * matrix.M20 + vector.y * matrix.M21 + vector.z * matrix.M22 + vector.w * matrix.M23;
            result.w = vector.x * matrix.M30 + vector.y * matrix.M31 + vector.z * matrix.M32 + vector.w * matrix.M33;

            return result;
        }

        // TODO: Find the signifance of having 2 overloads with opposite param lists that do transposed operations
        public static Vector4 operator *(Vector4 vector, Matrix4 matrix)
        {
            Vector4 result = new Vector4();

            result.x = vector.x * matrix.M00 + vector.y * matrix.M10 + vector.z * matrix.M20 + vector.w * matrix.M30;
            result.y = vector.x * matrix.M01 + vector.y * matrix.M11 + vector.z * matrix.M21 + vector.w * matrix.M31;
            result.z = vector.x * matrix.M02 + vector.y * matrix.M12 + vector.z * matrix.M22 + vector.w * matrix.M32;
            result.w = vector.x * matrix.M03 + vector.y * matrix.M13 + vector.z * matrix.M23 + vector.w * matrix.M33;

            return result;
        }

        /// <summary>
        ///		User to compare two Vector4 instances for equality.
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns>true or false</returns>
        public static bool operator ==(Vector4 left, Vector4 right)
        {
            return (left.x == right.x &&
                left.y == right.y &&
                left.z == right.z &&
                left.w == right.w);
        }

        /// <summary>
        ///		Used to add a Vector4 to another Vector4.
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Vector4 operator +(Vector4 left, Vector4 right)
        {
            return new Vector4(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w);
        }

        /// <summary>
        ///		Used to subtract a Vector4 from another Vector4.
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Vector4 operator -(Vector4 left, Vector4 right)
        {
            return new Vector4(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w);
        }
        /// <summary>
        ///		Used to negate the elements of a vector.
        /// </summary>
        /// <param name="left"></param>
        /// <returns></returns>
        public static Vector4 operator -(Vector4 left)
        {
            return new Vector4(-left.x, -left.y, -left.z, -left.w);
        }

        /// <summary>
        ///		User to compare two Vector4 instances for inequality.
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns>true or false</returns>
        public static bool operator !=(Vector4 left, Vector4 right)
        {
            return (left.x != right.x ||
                left.y != right.y ||
                left.z != right.z ||
                left.w != right.w);
        }

        /// <summary>
        ///		Used to access a Vector by index 0 = this.x, 1 = this.y, 2 = this.z, 3 = this.w.  
        /// </summary>
        /// <remarks>
        ///		Uses unsafe pointer arithmetic to reduce the code required.
        ///	</remarks>
        public float this[int index]
        {
            get
            {
                Debug.Assert(index >= 0 && index < 4, "Indexer boundaries overrun in Vector4.");

                // using pointer arithmetic here for less code.  Otherwise, we'd have a big switch statement.
                unsafe
                {
                    fixed (float* pX = &x)
                        return *(pX + index);
                }
            }
            set
            {
                Debug.Assert(index >= 0 && index < 4, "Indexer boundaries overrun in Vector4.");

                // using pointer arithmetic here for less code.  Otherwise, we'd have a big switch statement.
                unsafe
                {
                    fixed (float* pX = &x)
                        *(pX + index) = value;
                }
            }
        }

        #endregion

        #region Object overloads

        /// <summary>
        ///		Overrides the Object.ToString() method to provide a text representation of 
        ///		a Vector4.
        /// </summary>
        /// <returns>A string representation of a Vector4.</returns>
        public override string ToString()
        {
            return string.Format("<{0},{1},{2},{3}>", this.x, this.y, this.z, this.w);
        }

        /// <summary>
        ///		Provides a unique hash code based on the member variables of this
        ///		class.  This should be done because the equality operators (==, !=)
        ///		have been overriden by this class.
        ///		<p/>
        ///		The standard implementation is a simple XOR operation between all local
        ///		member variables.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.x.GetHashCode() ^ this.y.GetHashCode() ^ this.z.GetHashCode() ^ this.w.GetHashCode();
        }

        /// <summary>
        ///		Compares this Vector to another object.  This should be done because the 
        ///		equality operators (==, !=) have been overriden by this class.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            return obj is Vector4 && this == (Vector4)obj;
        }

        #endregion

        #region Clone

        public Vector4 Clone()
        {
            Vector4 clone = new Vector4();
            clone.x = x;
            clone.y = y;
            clone.z = z;
            clone.w = w;
            return clone;
        }

        #endregion

    }
}
